package com.yuwei.config;

import com.yuwei.security.handler.*;
import com.yuwei.service.SysUserService;
import com.yuwei.security.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;
import java.util.Map;

/**
 * web cookie 存储 JESSIONID： Security 授权认证配置类
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //登录成功处理逻辑
    @Autowired
    CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;

    //登录失败处理逻辑
    @Autowired
    CustomizeAuthenticationFailureHandler authenticationFailureHandler;

    //未登录用户访问无权限资源时的异常
//    @Autowired
//    CustomizeAuthenticationEntryPoint authenticationEntryPoint;

    //已登录用户访问无权限资源时的异常
    @Autowired
    CustomAccessDeineHandler customAccessDeineHandler;

    //会话失效(账号被挤下线)处理逻辑
    @Autowired
    CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    //登出成功处理逻辑
    @Autowired
    CustomizeLogoutSuccessHandler logoutSuccessHandler;

    //获取用户账号密码及权限信息、权限
    @Autowired
    UserDetailsServiceImpl userDetailsServiceImpl;


    /**
     * 设置Security默认的加密方式（强hash方式加密）
     * 常见的加密方式：
         * BCryptPasswordEncoder：Spring Security 推荐使用的，使用BCrypt强哈希方法来加密。
         * MessageDigestPasswordEncoder：用作传统的加密方式加密(支持 MD5、SHA-1、SHA-256...)
         * DelegatingPasswordEncoder：最常用的，根据加密类型id进行不同方式的加密，兼容性强
         * NoOpPasswordEncoder：明文， 不做加密
     * 其他
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 将能够获取：账号、密码、角色list的实现类对象交给AuthenticationManagerBuilder
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceImpl);
    }

    /**
     *
     * API 参考：
     *      authenticated() 允许认证的用户进行访问
     *      denyAll() 无条件拒绝所有访问
     *      fullyAuthenticated() 如果用户是完整认证的话（不是通过Remember-me功能认证的），就允许访问
     *      hasRole(String) 如果用户具备给定角色(用户组)的话,就允许访问
     *      hasAuthority(String) 如果用户具备给定权限的话就允许访问
     *      permitAll() 无条件允许访问
     *      rememberMe() 如果用户是通过Remember-me功能认证的，就允许访问
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        /**
         * 设置系统所有角色与权限
         *  1. security 根据request uri 匹配 permission后，得到role。
         *  2. 再根据userDetailsServiceImpl 中 new User（） 时传递的role list 做匹配。
         *  3. 如果角色在role list中，则权限认证成功，否则走没有权限处理。
         */
        initMatchersURLs(http);

        //关闭csrf跨域攻击防御
        http.cors().and().csrf().disable();
        //授权认证配置
        http.authorizeRequests().
            //antMatchers("/html/login.html").permitAll(). //放行：登录页面
            antMatchers("/yuwei/test").permitAll(). //放行匹配的url, 只为测试

            // antMatchers("/**").fullyAuthenticated().
            //anyRequest().authenticated().
            //denyAll()：所匹配的 URL 都不允许被访问。都需要进行：登录认证、权限的校验
            anyRequest().denyAll().

            //登录验证
            and().formLogin().loginPage("/html/login.html"). //配置自己的登录页
            loginProcessingUrl("/yuwei/login"). //自定义url，当访问/yuwei/login 时，security会自行验证账号密码
            usernameParameter("username").passwordParameter("password"). //请求报文参数必须为：username、password
            permitAll().//放行：/my/login、/html/login.html；允许所有用户访问
            successHandler(authenticationSuccessHandler).//登录成功处理逻辑
            failureHandler(authenticationFailureHandler).//登录失败处理逻辑;

            //登出处理
            and().logout().logoutUrl("/yuwei/logout"). //当访问自定义：/my/logout 时，security会自行注销session。
            permitAll().//放行：/my/logout；允许所有用户访问
            logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑
            deleteCookies("JSESSIONID").//登出之后删除cookie

            //已登录用户没权限-处理
            and().exceptionHandling().accessDeniedHandler(customAccessDeineHandler).
            //authenticationEntryPoint(authenticationEntryPoint).//匿名用户访问无权限资源时的异常处理

            //会话管理
            and().sessionManagement().
            maximumSessions(1).//同一账号同时登录最大用户数
            expiredSessionStrategy(sessionInformationExpiredStrategy);//会话失效(账号被挤下线)处理逻辑

        //添加自定义过滤器：可做 验证码判断
//        http.addFilterBefore(new YzmCodeFilter(), UsernamePasswordAuthenticationFilter.class);

    }


    @Autowired
    SysUserService sysUserService;

    /**
     * 设置菜单对应角色校验规则
     * @param http HttpSecurity
     * @throws Exception
     */
    private void initMatchersURLs(HttpSecurity http) throws Exception {
        //将所有权限和角色映射关系，设置到HttpSecurity中
        Map<String, List<String>> stringListMap = sysUserService.selectPermissions();
        for (Map.Entry<String, List<String>> stringListEntry : stringListMap.entrySet()) {
            String role = stringListEntry.getKey();
            List<String> permissions = stringListEntry.getValue();
            for (String permission : permissions) {
                //方式一：配置权限、role的绑定，适合 mysql表设计为：权限、角色、用户
                http.authorizeRequests().antMatchers(permission).hasAnyRole(role);
                //方式二：路径、权限的绑定，适合 mysql表设计为：权限、用户
                //antMatchers("/getUser").hasAuthority("query_user");
            }
        }
    }

    /**
     * 解决跨域请求
     * @return
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.addAllowedOrigin("*");
        configuration.addAllowedHeader("*");
        configuration.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /**
     * @Description: 配置放行的资源
     **/
    @Override
    public void configure(WebSecurity web) throws Exception {
        //此处可配置自己需要放行的资源
        web.ignoring().antMatchers("/html/**", "/static/**","/favicon.ico")
                // 此处给 swagger 放行；不需要权限能访问的资源
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui", "/configuration/security");
    }



}